import time
import json

from .const import *


class HttpRequest:
    '''http的请求对象'''
    def __init__(self, tcp_conn, tcp_addr, conn_time):
        '''用于解析http连接的请求行和请求头'''
        # 请求是否成功标志位
        self.success = True

        # 缓存读取缓冲区
        self._buffer = None
        self._body_buffer = None
        self._header_lines = None

        # 解析完毕的请求行和请求头数据
        self.method = None             # 请求方法
        self.url = None                # 去除协议和域名的URL
        self.path = None               # 去除查询字符串的URL
        self.protocol = None           # 请求协议版本
        self.headers = []              # 请求头使用列表存储 需要有序的数据
        self._tcp_conn = tcp_conn
        self.addr = tcp_addr
        self.conn_time = conn_time

        # 查询字符串 和 片段标识符
        self.query = None              # 查询字符串的内容
        self.fragment = None           # query 后 # 之后的 片段标识符

        # 路径参数 只有经过含有路径匹配的路由中间件才会有值
        self.params = {}

        # 经过 请求体解析类中间件 解析之后才会有的数据
        self.body = None

        # 接收请求数据
        self._recv_request_data()
        # 解析请求行
        self._load_http_request_line()
        # 解析请求头
        self._load_http_request_headers()
        # 解析 query 和 fragment
        self._load_query_and_fragment()

    async def getBody(self, size=1024*1024, timeout=20):
        '''
            获取请求体的方法
            超时时间默认是 20s
        '''
        body = self._body_buffer
        self._body_buffer = b''
        try:
            body += self._tcp_conn.recv(size)
        except BlockingIOError as e:
            pass
        return body

    def getHeader(self, key):
        '''
            获取单个请求头的方法
            由于请求头是允许重复key的
            所以本框架的 headers 采用 list存储
            返回的时候也返回一个 list
        '''
        # 大小写不敏感处理
        key = key.lower()
        # 循环获取请求头信息
        result = []
        for i in self.headers:
            if i[0] == key:
                result.append(i[1])
        return result
    
    def _load_http_request_line(self):
        ''' 解析请求行的方法 '''
        # 如果请求失败则返回
        if self.success == False:
            return
        try:
            self._buffer = self._buffer.decode('utf-8')
            all_line = self._buffer.split('\r\n')
            req_line = all_line[0]
            self.method, self.url, self.protocol = req_line.split(' ')
            self.path = self.url
            self._header_lines = all_line[1:]
        except Exception as e:
            # 解析请求行出错
            self.success = False
    
    def _load_http_request_headers(self):
        ''' 解析请求头的方法 '''
        # 如果请求失败则返回
        if self.success == False:
            return
        try:
            # 解析请求头 请求体不解析
            for line in self._header_lines:
                key, value = line.split(':', 1)
                # key 和 value 都去除前后空白字符
                key = key.strip()
                value = value.strip()
                # 将 key全部转为小写
                key = key.lower()
                self.headers.append((key, value))
        except Exception as e:
            # 解析过程出错
            self.success = False
        
    def _recv_request_data(self):
        ''' 接收请求体的方法 '''
        ''' 未来需要考虑接收一半客户端异常断开的异常处理 '''
        # 用于存储所有数据的缓冲区
        buffer = b''
        # 先获取 请求行 和 请求头 的部分 过多获取的存放在 self._readBuffer 中
        while True:
            try:
                data = self._tcp_conn.recv(KB)
                buffer += data
                # 此处可以优化为 idx 切片的方式
                if b'\r\n\r\n' in buffer:
                    # 裁剪
                    self._buffer, self._body_buffer = buffer.split(b'\r\n\r\n', 1)
                    break
                if time.time() - self.conn_time > 20:
                    # 超时
                    self.success = False
                    break
            except BlockingIOError as e:
                if time.time() - self.conn_time > 20:
                    # 超时
                    self.success = False
                    break
    
    def _load_query_and_fragment(self):
        ''' 解析 query 和 fragment '''
        self.path = self.url
        # 解析 fragment
        idx = self.path.find('#')
        if idx != -1:
            self.fragment = self.url[:idx]
            self.path = self.url[idx+1:]
        # 解析 query
        idx = self.path.find('?')
        if idx != -1:
            self.query = self.path[idx+1:]
            self.path = self.url[:idx]

class HttpResponse:
    '''
        http的响应对象
        这个类的 send和json 方法应该处理一下避免重复使用
    '''
    def __init__(self, tcp_conn):
        # 保存 tcp 连接对象
        self._tcp_conn = tcp_conn
        # 由于 header 允许存在重复键，所以这里使用 list 存储
        self.headers = [
            # 响应头声明字符编码为 utf-8
            ('Content-Type', 'text/html; charset=utf-8'),
        ]
        self._status = 200
        self._status_msg = 'OK'
        # 响应进度 0-未响应 1-响应了响应行 2-响应了响应头 3-响应了响应体
        self._response_progress = 0

    def status(self, status_code):
        '''用于设置响应码和响应状态'''
        self._status = status_code
        self._status_msg = HTTP_STATUS_CODES.get(status_code, 'unknown')
        return self

    def addHeader(self, header_key, header_value):
        '''用于直接添加响应头 允许重复 key 值'''
        # 处理成小写字母
        header_key = header_key.lower()
        # 添加到响应头中
        self.headers.append((header_key, header_value))

    def setHeader(self, header_key, header_value):
        '''用于设置响应头 不允许重复 key 值 重复则直接覆盖旧值'''
        # 处理成小写字母
        header_key = header_key.lower()
        i = 0
        while i < len(self.headers):
            if self.headers[i][0] == header_key:
                self.headers.pop(i)
                continue
            i += 1
        # 添加到响应头中
        self.headers.append((header_key, header_value))

    def send(self, data):
        '''用于设置响应体 并且发送完之后关闭http连接'''
        # 检查响应进度
        if self._response_progress == 0:
            # 响应响应行
            self._response_progress = 1
            self._tcp_conn.send(f'HTTP/1.1 {self._status} {self._status_msg}\r\n'.encode('utf-8'))
        if self._response_progress == 1:
            # 响应响应头
            self._response_progress = 2
            for i in self.headers:
                self._tcp_conn.send(f'{i[0]}: {i[1]}\r\n'.encode('utf-8'))
            self._tcp_conn.send('\r\n'.encode('utf-8'))
        if self._response_progress in (2, 3):
            # 响应响应体
            self._response_progress = 3
            if isinstance(data, str):
                self._tcp_conn.send(data.encode('utf-8'))
            else:
                self._tcp_conn.send(data)

    def json(self, obj):
        '''用于设置json类型的响应体 并且发送完之后关闭http连接'''
        self.setHeader('Content-Type', 'application/json; charset=utf-8')
        try:
            obj = json.dumps(obj)
        except TypeError as e:
            print('json.dumps 出现错误:', e)
            obj = '{}'
        except Exception as e:
            print('未知错误:', e)
            obj = '{}'
        self.send(obj)

    def sendFile(self, file_path):
        '''用于响应一个文件 并且发送完之后关闭http连接'''
        with open(file_path, 'rb') as f:
            data = f.read()
        self.send(data)
        self._tcp_conn.close()

    def sendStream(self):
        '''用于响应一个数据流 直到数据流发送完毕之后再断开http连接'''
        ...
